/**
 * 
 */
package br.edu.ufcg.msnlab2.misc;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import mathExpr.ExpressionConfiguration;
import mathExpr.evaluator.complexEvaluator.ComplexType;
import mathExpr.evaluator.realEvaluator.Real;
import mathExpr.evaluator.realEvaluator.RealType;
import mathExpr.parser.Expression;
import mathExpr.parser.ExpressionFactory;
import mathExpr.parser.javacc.ParseException;
import mathExpr.parser.javacc.Parser;
import mfc.field.Complex;
import br.edu.ufcg.msnlab2.InvalidFunctionException;

/**
 * @author 
 *
 */
public class FunctionImpl implements Function {
	
	private static final double DEFAULT_STEP = 0.01;

	private static final double DEFAULT_MAX = 10.d;

	private static final double DEFAULT_MIN = -10.0d;

	private String expression;
	
	private List<Point> points;
	
	private ExpressionConfiguration config;
	
	/**
	 * @param expression
	 */
	public FunctionImpl(String expression) throws InvalidFunctionException {
		this.expression = expression;
		this.config = new ExpressionConfiguration();
		ExpressionFactory ef = config.getExpressionFactory();
		Parser p = new Parser(new StringReader(expression), ef);
		try {
			
		    p.parse();
		    
		} catch (ParseException e) {
			System.out.println(expression);
		    throw new InvalidFunctionException(e.currentToken.next.toString(), e.currentToken.next.beginLine, e.currentToken.next.beginColumn);
		}
		
		config.setExpression(this.expression);
	}
	
	private FunctionImpl(Expression expression) {
		//FIXME search way to set string expression
		this.expression = null;
		this.config = new ExpressionConfiguration();
		config.setExpression(expression);
	}

	/**
	 * @return the expression
	 */
	public String getExpression() {
		return expression;
	}

	/** {@inheritDoc} */
	public double evaluate(double value) {
		config.setType(RealType.TYPE);
		config.defineVariable("x", new Real(value), RealType.TYPE);
		Real result = (Real) config.evaluateExpression();
		return result.getValue();
	}
	
	/** {@inheritDoc} */
	public Complex evaluate(Complex value) {
		config.setType(ComplexType.TYPE);
		config.defineVariable("x", new Complex(value), ComplexType.TYPE);
		Complex result = (Complex) config.evaluateExpression();
		return result;
	}

	/** {@inheritDoc} */
	public Function derivative() {
		
		return new FunctionImpl(config.differentiate("x"));
	}
	

	public Function derivativeY() {
		return new FunctionImpl(config.differentiate("y"));
	}
	
	/** {@inheritDoc} */
	public int getNumberOfVariables(){
		return config.getExpression().getEssentialVariables().size();
	}
	
	/** {@inheritDoc} */
	public List<Point> getPoints() {
		return getPoints(DEFAULT_MIN, DEFAULT_MAX, DEFAULT_STEP);
	}

	/** {@inheritDoc} */
	public List<Point> getPoints(double min, double max, double step) {
		double actual = min;
		List<Point> result = new ArrayList<Point>();
		while (actual <= max){
			result.add(new PointImpl(actual, this.evaluate(actual)));
			actual = actual + step;
		}
		return result;
	}
	
	/** {@inheritDoc} */
	public void setPoints(List<Point> points){
		this.points = points;
	}
	

	/** {@inheritDoc} */
	public double evaluate(double x, double y) {
		config.setType(RealType.TYPE);
		config.defineVariable("x", new Real(x), RealType.TYPE);
		config.setType(RealType.TYPE);
		config.defineVariable("y", new Real(y), RealType.TYPE);
		Real result = (Real) config.evaluateExpression();
		return result.getValue();
	}
	
	/** {@inheritDoc} */
	@Override
	public double evaluate(Map<String, List<Double>> sampledPoints, int index) {
		if(index < 0){
			throw new RuntimeException("Invalid points index!");
		}
		
		config.setType(RealType.TYPE);
		
		for(Entry<String, List<Double>> entry : sampledPoints.entrySet()){
			double value = entry.getValue().get(index);
			config.defineVariable(entry.getKey(), new Real(value), RealType.TYPE);
		}
		return ((Real)config.evaluateExpression()).getValue();
	}
}
